package com.example.netcamerastreamdemo;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Hashtable;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;

import static android.media.MediaFormat.KEY_HEIGHT;
import static android.media.MediaFormat.KEY_WIDTH;
import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC;

public class VideoStreamPlayer {
    private static final String TAG = "VideoStreamPlayer";

    private static final String STREAM_START_FLAG = "VSDK_CMD_TX_RT_VIDEO_START";
    private static final String STREAM_STOP_FLAG = "VSDK_CMD_TX_RT_VIDEO_STOP";
    private static final String STREAM_HEART_BEAT_FLAG = "HB";
    private static final int TIMEOUT_VALUE = 30;

    private WebSocket mWebSocket;
    private Gson mGson = new Gson();
    private Hashtable<Integer, byte[]> mStreamData = new Hashtable<>();
    private int mIndex = -1;
    private SurfaceView mSurfaceView;
    private DecodeThread mDecodeThread;
    private String mWebsocketUrl;
    private MsgData mAuthData;
    private boolean isPlaying = false;
    private boolean isWebsocketConnected = false;
    private long mStreamStartTime = 0;

    public VideoStreamPlayer(@NonNull SurfaceView mSurfaceView,
                             @NonNull String url,
                             @NonNull MsgData authData) {
        this.mSurfaceView = mSurfaceView;
        this.mWebsocketUrl = url;
        this.mAuthData = authData;
    }

    public boolean prepare(){
        if (mSurfaceView == null){
            return false;
        }
        if (mWebsocketUrl.isEmpty()){
            return false;
        }
        if (mAuthData == null){
            return false;
        }
        initWebSocket();
        return true;
    }

    public void startPlay(){
        if (isPlaying){
            return;
        }
        isPlaying = true;
        mDecodeThread = new DecodeThread();
        mDecodeThread.start();
        sendStartMsg();
    }

    public void stopPlay(){
        if (!isPlaying){
            return;
        }
        reset();
        sendStopVideoMsg();
    }

    public void release(){
        mWebSocket.cancel();
    }

    private void reset(){
        isPlaying = false;
        mStreamData.clear();
        mIndex = -1;
        mDecodeThread.stopLopper();
    }

    private void sendStartMsg() {
        if (isWebsocketConnected) {
            MsgData startVideoMsg = new MsgData(
                    STREAM_START_FLAG,
                    mAuthData.dev_guid,
                    mAuthData.video_chnn,
                    mAuthData.sub_chnn,
                    mAuthData.rec_path);
            mWebSocket.send(mGson.toJson(startVideoMsg));
        }
    }

    private void sendStopVideoMsg(){
        if (isWebsocketConnected) {
            MsgData stopVideoMsg = new MsgData(
                    STREAM_STOP_FLAG,
                    mAuthData.dev_guid,
                    mAuthData.video_chnn,
                    mAuthData.sub_chnn);
            mWebSocket.send(mGson.toJson(stopVideoMsg));
        }
    }

    private void sendHeartBeatMsg(){
        if (isWebsocketConnected) {
            MsgData heartBeatMsg = new MsgData();
            heartBeatMsg.cmd = STREAM_HEART_BEAT_FLAG;
            mWebSocket.send(mGson.toJson(heartBeatMsg));
        }
    }

    private void initWebSocket(){
        OkHttpClient mClient = new OkHttpClient.Builder()
                .readTimeout(TIMEOUT_VALUE, TimeUnit.SECONDS)//设置读取超时时间
                .writeTimeout(TIMEOUT_VALUE, TimeUnit.SECONDS)//设置写的超时时间
                .connectTimeout(TIMEOUT_VALUE, TimeUnit.SECONDS)//设置连接超时时间
                .build();

        Request request = new Request.Builder().get().url(mWebsocketUrl).build();
        mWebSocket = mClient.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                Log.i(TAG, "---onOpen===");
                isWebsocketConnected = true;
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                Log.i(TAG, "---onMessage===" + text);
                if (text.contains(STREAM_START_FLAG)){
                    mStreamStartTime = System.currentTimeMillis();
                }
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
                Log.i(TAG, "---onMessage===" + bytes.size());
                mIndex++;
                mStreamData.put(mIndex, bytes.substring(44).toByteArray());
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
                Log.i(TAG, "---onClosing===" + reason);
                isWebsocketConnected = false;
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                Log.i(TAG, "---onClosed===" + reason);
                isWebsocketConnected = false;
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                super.onFailure(webSocket, t, response);
                Log.i(TAG, "---onFailure===" + t.toString());
                isWebsocketConnected = false;
                initWebSocket();
            }
        });
    }

    private class DecodeThread extends Thread{
        private MediaCodec decoder;
        private int mWidth = 640;
        private int mHeight = 480;
        private int decodeCount = 0;
        private MediaFormat outFormat;

        @Override
        public void run() {
            while (decodeCount == 0){
                runDecode();
            }
        }

        private void runDecode(){
            if (decoder == null){
                try {
                    MediaFormat format = MediaFormat.createVideoFormat(MIMETYPE_VIDEO_AVC, mWidth, mHeight);
                    decoder = MediaCodec.createDecoderByType(MIMETYPE_VIDEO_AVC);
                    decoder.configure(format, mSurfaceView.getHolder().getSurface(), null, 0);
                    decoder.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            while (isPlaying){
                long stepTime = (System.currentTimeMillis() - mStreamStartTime) / 1000;
                if (stepTime >= 30){
                    mStreamStartTime = System.currentTimeMillis();
                    sendHeartBeatMsg();
                }
                if (null == mStreamData.get(decodeCount)){
                    continue;
                }
                byte[] frameByte = mStreamData.get(decodeCount).clone();
                int inputIndex = decoder.dequeueInputBuffer(10000);
                if (inputIndex >= 0){
                    ByteBuffer byteBuffer = decoder.getInputBuffer(inputIndex);
                    byteBuffer.put(frameByte);
                    decoder.queueInputBuffer(inputIndex, 0, frameByte.length, 0, 0);
                }
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                int outputIndex = decoder.dequeueOutputBuffer(bufferInfo, 0);
                if (outputIndex >= 0){
                    decoder.releaseOutputBuffer(outputIndex, true);
                }
                if (outFormat == null) {
                    outFormat = decoder.getOutputFormat();
                    Log.i(TAG, "---outFormat===" + outFormat.toString());
                    Log.i(TAG, "---outFormat=KEY_WIDTH==" + outFormat.getInteger(KEY_WIDTH));
                    Log.i(TAG, "---outFormat=KEY_HEIGHT==" + outFormat.getInteger(KEY_HEIGHT));
//                    mSurfaceView.getHolder().setFixedSize(outFormat.getInteger(KEY_WIDTH));
                }
                mStreamData.remove(decodeCount);
                decodeCount++;
            }
        }

        private void stopLopper(){
            decoder.reset();
            decoder.release();
            decodeCount = -1;
        }
    }

    public static class MsgData{
        public MsgData() {

        }

        public MsgData(String cmd, String dev_guid, int video_chnn, int sub_chnn, String rec_path) {
            this.cmd = cmd;
            this.dev_guid = dev_guid;
            this.video_chnn = video_chnn;
            this.sub_chnn = sub_chnn;
            this.rec_path = rec_path;
        }

        public MsgData(String cmd, String dev_guid, int video_chnn, int sub_chnn) {
            this.cmd = cmd;
            this.dev_guid = dev_guid;
            this.video_chnn = video_chnn;
            this.sub_chnn = sub_chnn;
        }

        public String cmd;
        public String dev_guid;
        public int video_chnn;
        public int sub_chnn;
        public String rec_path;
    }
}
